using System;
using System.Data;
using System.Collections;
using System.IO;
using System.Text.RegularExpressions;
using System.Text;
using System.Collections.Generic;

/*
    CssDom Library
    Copyright (C) 2007-2011  Ferenc Veres

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

namespace WPS.Libraries.CssDom
{
	/// <summary>
	/// The CSSStyleSheet class is a concrete interface used to represent a CSS style sheet 
	/// i.e., a style sheet whose content type is "text/css".
	/// </summary>
	public class CSSStyleSheet
	{
		#region Constants

		/// <summary>
		/// Hardcoded list of CSS property names that should be repeated with -moz- perfix.
		/// </summary>
		public static readonly HashSet<string> MozPrefix = new HashSet<string>();

		/// <summary>
		/// Hardcoded list of CSS property names that should be repeated with -webkit- perfix.
		/// </summary>
		public static readonly HashSet<string> WebkitPrefix = new HashSet<string>();

		/// <summary>
		/// Static constructor to initialize static constants.
		/// </summary>
		static CSSStyleSheet()
		{
			// Forces some CSS properties to generate vendor prefixes.
			MozPrefix.Add("box-shadow");
			WebkitPrefix.Add("box-shadow");
		}
		#endregion

		#region CSSStyleSheet interface properties

		/// <summary>
		/// If this style sheet comes from an @import rule, the ownerRule attribute will contain the CSSImportRule. 
		/// </summary>
		public CSSRule ownerRule
		{
			get
			{
				return null;
			}
		}

		/// <summary>
		/// The list of all CSS rules contained within the style sheet. This includes both rule sets and at-rules.
		/// </summary>
		public CSSRuleList cssRules;

		#endregion

		#region Initialization

		/// <summary>
		/// Creates an empty CSSStyleSheet object to handle CSS files.
		/// </summary>
		public CSSStyleSheet()
		{
			this.cssRules = new CSSRuleList();
		}

		#endregion

		#region Load CSS file

		/// <summary>
		/// Load the specified css file.
		/// </summary>
		/// <param name="fileName">Phisical path of a CSS file.</param>
		/// <returns>True on success, false on failure.</returns>
		public bool Load(string fileName)
		{
			string cssFile;
			// Check if there is a CSS file for this site
			if(File.Exists(fileName))
			{
				try
				{
					// Yep file exists, load the CSS file!
					cssFile = File.ReadAllText(fileName);
				}
				catch
				{
					return false;
				}

				LoadString(cssFile);
				return true;
			}
			return false;
		}

		/// <summary>
		/// Load CSS rules from the passed string.
		/// </summary>
		/// <param name="cssContents">Css style rule declarations as in a CSS file.</param>
		public void LoadString(string cssContents)
		{
			string[] cssBlocks;

			// Emtpy previously loaded CSS if any.
			this.cssRules = new CSSRuleList();

			// Remove linefeeds
			cssContents = cssContents.Replace("\r\n", "");
			cssContents = cssContents.Replace("\r", "");
			cssContents = cssContents.Replace("\n", "");

			// Remove comments
			Regex rxComments;
			rxComments = new Regex(@"/\*.*?\*/");
			cssContents = rxComments.Replace(cssContents, "");

			// Parse this file content as the user-custom "wysiwyg.css"

			// Split each class definition to array (separated by "}")
			cssBlocks = cssContents.Split('}');

			// Loop on each definition
			for(int i = 0; i < cssBlocks.Length; i++)
			{
				// "selector { rule1; rule2;"  (No closing "}")
				string cssBlock = cssBlocks[i];

				if(cssBlock != "")
				{
					// Media rules are double nested "@media... {  selector { rules } }
					if(cssBlock.IndexOf('{') != cssBlock.LastIndexOf('{'))
					{
						string noMedia = cssBlock.Substring(cssBlock.IndexOf('{') + 1);
						insertRule(noMedia + "}", -1);
						i++; // Skip media closing empty string "}}"
					}
					else
					{
						insertRule(cssBlock + "}", -1);
					}
				}
			}

		}

		#endregion

		#region Save CSS File

		/// <summary>
		/// Save the CSSDom object as a nice formatted Css file.
		/// </summary>
		/// <param name="fileName">Output file name, with complete path.</param>
		public void Save(string fileName)
		{
			this.Save(fileName, false);
		}
		/// <summary>
		/// Save the CSSDom object as a nice formatted Css file.
		/// </summary>
		/// <param name="fileName">Output file name, with complete path.</param>
		/// <param name="skipEmptyRules">True will not save empty rules to the file.</param>
		public void Save(string fileName, bool skipEmptyRules)
		{
			string content = "";
			string name;
			string value;
			string priority = "";
			CSSStyleRule rule;

			try
			{
				// We take care of formatting, so do a loop on rules and then properties

				// Loop css rules
				for(int i = 0; i < this.cssRules.length; i++)
				{
					rule = (CSSStyleRule)this.cssRules[i];

					// Skip empty rules
					if(skipEmptyRules && rule.style.length == 0)
					{
						continue;
					}

					content += rule.selectorText + "\r\n{\r\n";

					// Loop properties in this rule
					for(int j = 0; j < rule.style.length; j++)
					{
						name = rule.style.item(j);

						// Skip vendor prefixed rules if they will be or were overwritten
						if(name.StartsWith("-moz-")
							&& CSSStyleSheet.MozPrefix.Contains(name.Replace("-moz-", ""))
							&& rule.style.defines(name.Replace("-moz-", "")))
						{
							continue;
						}
						if(name.StartsWith("-webkit-")
							&& CSSStyleSheet.WebkitPrefix.Contains(name.Replace("-webkit-", ""))
							&& rule.style.defines(name.Replace("-webkit-", "")))
						{
							continue;
						}

						value = rule.style.getPropertyValue(name);
						priority = rule.style.getPropertyPriority(name);

						// Workaround for old IE, additional "border-spacing" property
						if(name == "border-spacing")
						{
							Regex numValueRx = new Regex("^.*?([0-9]+).*$");
							Match match = numValueRx.Match(value);
							if(match.Success)
							{
								content += "\t" + name + ": expression(cellSpacing=" + match.Groups[1].Value + ")";
								content += (priority != "" ? " " + priority : "");
								content += ";\r\n";
							}
						}

						// Properties that need vendor prefix
						if(CSSStyleSheet.MozPrefix.Contains(name)) 
						{
							content += "\t-moz-" + name + ": " + value;
							content += (priority != "" ? " " + priority : "");
							content += ";\r\n";
						}
						if(CSSStyleSheet.WebkitPrefix.Contains(name))
						{
							content += "\t-webkit-" + name + ": " + value;
							content += (priority != "" ? " " + priority : "");
							content += ";\r\n";
						}

						// Write property value
						content += "\t" + name + ": " + value;
						content += (priority != "" ? " " + priority : "");
						content += ";\r\n";
					}
					content += "}\r\n\r\n";
				}
			}
			catch(Exception ex)
			{
				throw new Exception("Error in generating CSS output.Succeeded content:\r\n" + content, ex);
			}

			// Remove readonly and save CSS
			if(File.Exists(fileName))
			{
				FileInfo file = new FileInfo(fileName);
				file.Attributes = FileAttributes.Normal;
			}
			File.WriteAllText(fileName, content, Encoding.UTF8);
		}

		#endregion

		#region CSSStyleSheet interface public functions

		/// <summary>
		/// Used to insert a new rule into the style sheet. CSSStyleSheet class calls this, in general,
		/// the CSSStyleSheet's insertRule should be called, this is just the internal implementation.
		/// </summary>
		/// <param name="rule">The parsable text representing the rule. For rule sets this contains both the selector and the style declaration. For at-rules, this specifies both the at-identifier and the rule content.</param>
		/// <param name="index">The index within the style sheet's rule list of the rule before which to insert the specified rule. If the specified index is equal to the length of the style sheet's rule collection, the rule will be added to the end of the style sheet.</param>
		/// <returns>The index within the style sheet's rule collection of the newly inserted rule.</returns>
		public int insertRule(string rule, int index)
		{
			return cssRules.insertRule(rule, index);
		}

		/// <summary>
		/// Used to delete a rule from the style sheet.
		/// </summary>
		/// <param name="index">The index within the style sheet's rule list of the rule to remove.</param>
		public void deleteRule(int index)
		{
			cssRules.deleteRule(index);
		}

		#endregion

	}
}